package org.checkerframework.checker.index.upperbound;

import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.index.IndexAbstractTransfer;
import org.checkerframework.checker.index.IndexRefinementInfo;
import org.checkerframework.checker.index.Subsequence;
import org.checkerframework.checker.index.inequality.LessThanAnnotatedTypeFactory;
import org.checkerframework.checker.index.qual.LessThan;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.index.qual.SubstringIndexFor;
import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf;
import org.checkerframework.checker.index.upperbound.UBQualifier.UpperBoundUnknownQualifier;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.ValueCheckerUtils;
import org.checkerframework.dataflow.analysis.RegularTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.CaseNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
import org.checkerframework.dataflow.cfg.node.TypeCastNode;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.MethodCall;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationUtils;

/**
 * Contains the transfer functions for the upper bound type system, a part of the Index Checker.
 * This class implements the following refinement rules:
 *
 * <ul>
 *   <li>1. Refine the type of expressions used as an array dimension to be less than length of the
 *       array to which the new array is assigned. For example, in {@code int[] array = new
 *       int[expr];}, the type of expr is {@code @LTEqLength("array")}.
 *   <li>2. If {@code other * node} has type {@code typeOfMultiplication}, then if {@code other} is
 *       positive, then {@code node} is {@code typeOfMultiplication}.
 *   <li>3. If {@code other * node} has type {@code typeOfMultiplication}, if {@code other} is
 *       greater than 1, then {@code node} is {@code typeOfMultiplication} plus 1.
 *   <li>4. Given a subtraction node, {@code node}, that is known to have type {@code
 *       typeOfSubtraction}. An offset can be applied to the left node (i.e. the left node has the
 *       same type, but with an offset based on the right node).
 *   <li>5. In an addition expression, refine the two operands based on the type of the whole
 *       expression with appropriate offsets.
 *   <li>6. If an addition expression has a type that is less than length of an array, and one of
 *       the operands is non-negative, then the other is less than or equal to the length of the
 *       array.
 *   <li>7. If an addition expression has a type that is less than length of an array, and one of
 *       the operands is positive, then the other is also less than the length of the array.
 *   <li>8. if x &lt; y, and y has a type that is related to the length of an array, then x has the
 *       same type, with an offset that is one less.
 *   <li>9. if x &le; y, and y has a type that is related to the length of an array, then x has the
 *       same type.
 *   <li>10. refine the subtrahend in a subtraction which is greater than or equal to a certain
 *       offset. The type of the subtrahend is refined to the type of the minuend with the offset
 *       added.
 *   <li>11. if two variables are equal, they have the same type
 *   <li>12. If one node in a != expression is an sequence length field or method access (optionally
 *       with a constant offset subtracted) and the other node is less than or equal to that
 *       sequence length (minus the offset), then refine the other node's type to less than the
 *       sequence length (minus the offset).
 *   <li>13. If some Node a is known to be less than the length of some array, x, then, the type of
 *       a + b, is {@code @LTLengthOf(value="x", offset="-b")}. If b is known to be less than the
 *       length of some other array, y, then the type of a + b is {@code @LTLengthOf(value={"x",
 *       "y"}, offset={"-b", "-a"})}.
 *   <li>14. If a is known to be less than the length of x when some offset, o, is added to a (the
 *       type of a is {@code @LTLengthOf(value="x", offset="o"))}, then the type of a + b is
 *       {@code @LTLengthOf(value="x",offset="o - b")}. (Note, if "o - b" can be computed, then it
 *       is and the result is used in the annotation.)
 *   <li>15. If expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length")} int
 *       and expression j is less than or equal to the length of f1, then the type of i + j is
 *       {@code @LTLengthOf("f2")}.
 *   <li>16. If some Node a is known to be less than the length of some sequence x, then the type of
 *       a - b is {@code @LTLengthOf(value="x", offset="b")}.
 *   <li>17. If some Node a is known to be less than the length of some sequence x, and if b is
 *       non-negative or positive, then a - b should keep the types of a.
 *   <li>18. The type of a sequence length access (i.e. array.length) is
 *       {@code @LTLength(value={"array"...}, offset="-1")} where "array"... is the set of all
 *       sequences that are the same length (via the SameLen checker) as "array"
 *   <li>19. If n is an array length field access, then the type of a.length is the glb of
 *       {@code @LTEqLengthOf("a")} and the value of a.length in the store.
 *   <li>20. If n is a String.length() method invocation, then the type of s.length() is the glb of
 *       {@code @LTEqLengthOf("s")} and the value of s.length() in the store.
 * </ul>
 */
public class UpperBoundTransfer extends IndexAbstractTransfer {

  /** The type factory associated with this transfer function. */
  private final UpperBoundAnnotatedTypeFactory atypeFactory;

  /** The int TypeMirror. */
  private final TypeMirror intTM;

  /**
   * Creates a new UpperBoundTransfer.
   *
   * @param analysis the analysis for this transfer function
   */
  public UpperBoundTransfer(CFAnalysis analysis) {
    super(analysis);
    atypeFactory = (UpperBoundAnnotatedTypeFactory) analysis.getTypeFactory();
    intTM = atypeFactory.types.getPrimitiveType(TypeKind.INT);
  }

  /**
   * Case 1: Refine the type of expressions used as an array dimension to be less than length of the
   * array to which the new array is assigned. For example, in "int[] array = new int[expr];", the
   * type of expr is @LTEqLength("array").
   */
  @Override
  public TransferResult<CFValue, CFStore> visitAssignment(
      AssignmentNode node, TransferInput<CFValue, CFStore> in) {
    TransferResult<CFValue, CFStore> result = super.visitAssignment(node, in);

    Node expNode = node.getExpression();

    // strip off typecast if any
    Node expNodeSansCast =
        (expNode instanceof TypeCastNode) ? ((TypeCastNode) expNode).getOperand() : expNode;
    // null if right-hand-side is not an array creation expression
    ArrayCreationNode acNode =
        (expNodeSansCast instanceof ArrayCreationNode) ? (ArrayCreationNode) expNodeSansCast : null;

    if (acNode != null) {
      // Right-hand side of assignment is an array creation expression
      List<Node> nodeList = acNode.getDimensions();
      if (nodeList.isEmpty()) {
        return result;
      }
      Node dim = acNode.getDimension(0);

      UBQualifier previousQualifier = getUBQualifier(dim, in);
      JavaExpression arrayExpr = JavaExpression.fromNode(node.getTarget());
      String arrayString = arrayExpr.toString();
      LessThanLengthOf newInfo =
          (LessThanLengthOf) UBQualifier.createUBQualifier(arrayString, "-1");
      UBQualifier combined = previousQualifier.glb(newInfo);
      AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(combined);

      JavaExpression dimExpr = JavaExpression.fromNode(dim);
      result.getRegularStore().insertValue(dimExpr, newAnno);
      propagateToOperands(newInfo, dim, in, result.getRegularStore());
    }
    return result;
  }

  /**
   * {@code node} is known to be {@code typeOfNode}. If the node is a plus or a minus then the types
   * of the left and right operands can be refined to include offsets. If the node is a
   * multiplication, its operands can also be refined. See {@link
   * #propagateToAdditionOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)}, {@link
   * #propagateToSubtractionOperands(LessThanLengthOf, NumericalSubtractionNode, TransferInput,
   * CFStore)}, and {@link #propagateToMultiplicationOperand(LessThanLengthOf, Node, Node,
   * TransferInput, CFStore)} for details.
   */
  private void propagateToOperands(
      LessThanLengthOf typeOfNode, Node node, TransferInput<CFValue, CFStore> in, CFStore store) {
    if (node instanceof NumericalAdditionNode) {
      Node right = ((NumericalAdditionNode) node).getRightOperand();
      Node left = ((NumericalAdditionNode) node).getLeftOperand();
      propagateToAdditionOperand(typeOfNode, left, right, in, store);
      propagateToAdditionOperand(typeOfNode, right, left, in, store);
    } else if (node instanceof NumericalSubtractionNode) {
      propagateToSubtractionOperands(typeOfNode, (NumericalSubtractionNode) node, in, store);
    } else if (node instanceof NumericalMultiplicationNode) {
      if (atypeFactory.hasLowerBoundTypeByClass(node, Positive.class)) {
        Node right = ((NumericalMultiplicationNode) node).getRightOperand();
        Node left = ((NumericalMultiplicationNode) node).getLeftOperand();
        propagateToMultiplicationOperand(typeOfNode, left, right, in, store);
        propagateToMultiplicationOperand(typeOfNode, right, left, in, store);
      }
    }
  }

  /**
   * {@code other} times {@code node} is known to be {@code typeOfMultiplication}.
   *
   * <p>This implies that if {@code other} is positive, then {@code node} is {@code
   * typeOfMultiplication}. If {@code other} is greater than 1, then {@code node} is {@code
   * typeOfMultiplication} plus 1. These are cases 2 and 3, respectively.
   */
  private void propagateToMultiplicationOperand(
      LessThanLengthOf typeOfMultiplication,
      Node node,
      Node other,
      TransferInput<CFValue, CFStore> in,
      CFStore store) {
    if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) {
      Long minValue =
          ValueCheckerUtils.getMinValue(
              other.getTree(), atypeFactory.getValueAnnotatedTypeFactory());
      if (minValue != null && minValue > 1) {
        typeOfMultiplication = (LessThanLengthOf) typeOfMultiplication.plusOffset(1);
      }
      UBQualifier qual = getUBQualifier(node, in);
      UBQualifier newQual = qual.glb(typeOfMultiplication);
      JavaExpression je = JavaExpression.fromNode(node);
      store.insertValue(je, atypeFactory.convertUBQualifierToAnnotation(newQual));
    }
  }

  /**
   * The subtraction node, {@code node}, is known to be {@code typeOfSubtraction}.
   *
   * <p>This means that the left node is less than or equal to the length of the array when the
   * right node is subtracted from the left node. Note that unlike {@link
   * #propagateToAdditionOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)} and {@link
   * #propagateToMultiplicationOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)}, this
   * method takes the NumericalSubtractionNode instead of the two operand nodes. This implements
   * case 4.
   *
   * @param typeOfSubtraction type of node
   * @param node subtraction node that has typeOfSubtraction
   * @param in a TransferInput
   * @param store location to store the refined type
   */
  private void propagateToSubtractionOperands(
      LessThanLengthOf typeOfSubtraction,
      NumericalSubtractionNode node,
      TransferInput<CFValue, CFStore> in,
      CFStore store) {
    UBQualifier left = getUBQualifier(node.getLeftOperand(), in);
    UBQualifier newInfo = typeOfSubtraction.minusOffset(node.getRightOperand(), atypeFactory);

    UBQualifier newLeft = left.glb(newInfo);
    JavaExpression leftJe = JavaExpression.fromNode(node.getLeftOperand());
    store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(newLeft));
  }

  /**
   * Refines the type of {@code operand} to {@code typeOfAddition} plus {@code other}. If {@code
   * other} is non-negative, then {@code operand} also less than the length of the arrays in {@code
   * typeOfAddition}. If {@code other} is positive, then {@code operand} also less than the length
   * of the arrays in {@code typeOfAddition} plus 1. These are cases 5, 6, and 7.
   *
   * @param typeOfAddition type of {@code operand + other}
   * @param operand the Node to refine
   * @param other the Node added to {@code operand}
   * @param in a TransferInput
   * @param store location to store the refined types
   */
  private void propagateToAdditionOperand(
      LessThanLengthOf typeOfAddition,
      Node operand,
      Node other,
      TransferInput<CFValue, CFStore> in,
      CFStore store) {
    UBQualifier operandQual = getUBQualifier(operand, in);
    UBQualifier newQual = operandQual.glb(typeOfAddition.plusOffset(other, atypeFactory));

    // If the node is NonNegative, add an LTEL to the qual. If Positive, add an LTL.
    if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) {
      newQual = newQual.glb(typeOfAddition.plusOffset(1));
    } else if (atypeFactory.hasLowerBoundTypeByClass(other, NonNegative.class)) {
      newQual = newQual.glb(typeOfAddition);
    }
    JavaExpression operandJe = JavaExpression.fromNode(operand);
    store.insertValue(operandJe, atypeFactory.convertUBQualifierToAnnotation(newQual));
  }

  /**
   * Case 8: if x &lt; y, and y has a type that is related to the length of an array, then x has the
   * same type, with an offset that is one less.
   */
  @Override
  protected void refineGT(
      Node larger,
      AnnotationMirror largerAnno,
      Node smaller,
      AnnotationMirror smallerAnno,
      CFStore store,
      TransferInput<CFValue, CFStore> in) {
    // larger > smaller
    UBQualifier largerQual =
        UBQualifier.createUBQualifier(largerAnno, (UpperBoundChecker) atypeFactory.getChecker());
    // larger + 1 >= smaller
    UBQualifier largerQualPlus1 = largerQual.plusOffset(1);
    UBQualifier rightQualifier =
        UBQualifier.createUBQualifier(smallerAnno, (UpperBoundChecker) atypeFactory.getChecker());
    UBQualifier refinedRight = rightQualifier.glb(largerQualPlus1);

    if (largerQualPlus1.isLessThanLengthQualifier()) {
      propagateToOperands((LessThanLengthOf) largerQualPlus1, smaller, in, store);
    }

    refineSubtrahendWithOffset(larger, smaller, true, in, store);

    JavaExpression rightJe = JavaExpression.fromNode(smaller);
    store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight));
  }

  /**
   * Case 9: if x &le; y, and y has a type that is related to the length of an array, then x has the
   * same type.
   */
  @Override
  protected void refineGTE(
      Node left,
      AnnotationMirror leftAnno,
      Node right,
      AnnotationMirror rightAnno,
      CFStore store,
      TransferInput<CFValue, CFStore> in) {
    UBQualifier leftQualifier =
        UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker());
    UBQualifier rightQualifier =
        UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker());
    UBQualifier refinedRight = rightQualifier.glb(leftQualifier);

    if (leftQualifier.isLessThanLengthQualifier()) {
      propagateToOperands((LessThanLengthOf) leftQualifier, right, in, store);
    }

    refineSubtrahendWithOffset(left, right, false, in, store);

    JavaExpression rightJe = JavaExpression.fromNode(right);
    store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight));
  }

  /**
   * Refines the subtrahend in a subtraction which is greater than or equal to a certain offset. The
   * type of the subtrahend is refined to the type of the minuend with the offset added. This is
   * case 10.
   *
   * <p>This is based on the fact that if {@code (minuend - subtrahend) >= offset}, and {@code
   * minuend + o < l}, then {@code subtrahend + o + offset < l}.
   *
   * <p>If {@code gtNode} is not a {@link NumericalSubtractionNode}, the method does nothing.
   *
   * @param gtNode the node that is greater or equal to the offset
   * @param offsetNode a node part of the offset
   * @param offsetAddOne if true, add one to the offset
   * @param in input of the transfer function
   * @param store location to store the refined types
   */
  private void refineSubtrahendWithOffset(
      Node gtNode,
      Node offsetNode,
      boolean offsetAddOne,
      TransferInput<CFValue, CFStore> in,
      CFStore store) {
    if (gtNode instanceof NumericalSubtractionNode) {
      NumericalSubtractionNode subtractionNode = (NumericalSubtractionNode) gtNode;

      Node minuend = subtractionNode.getLeftOperand();
      UBQualifier minuendQual = getUBQualifier(minuend, in);
      Node subtrahend = subtractionNode.getRightOperand();
      UBQualifier subtrahendQual = getUBQualifier(subtrahend, in);

      UBQualifier newQual =
          subtrahendQual.glb(
              minuendQual.plusOffset(offsetNode, atypeFactory).plusOffset(offsetAddOne ? 1 : 0));
      JavaExpression subtrahendJe = JavaExpression.fromNode(subtrahend);
      store.insertValue(subtrahendJe, atypeFactory.convertUBQualifierToAnnotation(newQual));
    }
  }

  /** Implements case 11. */
  @Override
  protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo(
      TransferResult<CFValue, CFStore> res,
      Node firstNode,
      Node secondNode,
      CFValue firstValue,
      CFValue secondValue,
      boolean notEqualTo) {
    TransferResult<CFValue, CFStore> result =
        super.strengthenAnnotationOfEqualTo(
            res, firstNode, secondNode, firstValue, secondValue, notEqualTo);
    IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, firstNode, secondNode);
    if (rfi.leftAnno == null || rfi.rightAnno == null) {
      return result;
    }

    CFStore equalsStore = notEqualTo ? rfi.elseStore : rfi.thenStore;
    CFStore notEqualStore = notEqualTo ? rfi.thenStore : rfi.elseStore;

    refineEq(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, equalsStore);

    refineNeqSequenceLength(rfi.left, rfi.right, rfi.rightAnno, notEqualStore);
    refineNeqSequenceLength(rfi.right, rfi.left, rfi.leftAnno, notEqualStore);
    return rfi.newResult;
  }

  /** Refines the type of the left and right node to glb of the left and right annotation. */
  private void refineEq(
      Node left, AnnotationMirror leftAnno, Node right, AnnotationMirror rightAnno, CFStore store) {
    UBQualifier leftQualifier =
        UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker());
    UBQualifier rightQualifier =
        UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker());
    UBQualifier glb = rightQualifier.glb(leftQualifier);
    AnnotationMirror glbAnno = atypeFactory.convertUBQualifierToAnnotation(glb);

    List<Node> internalsRight = splitAssignments(right);
    for (Node internal : internalsRight) {
      JavaExpression rightJe = JavaExpression.fromNode(internal);
      store.insertValue(rightJe, glbAnno);
    }

    List<Node> internalsLeft = splitAssignments(left);
    for (Node internal : internalsLeft) {
      JavaExpression leftJe = JavaExpression.fromNode(internal);
      store.insertValue(leftJe, glbAnno);
    }
  }

  /**
   * If lengthAccess node is an sequence length field or method access (optionally with a constant
   * offset subtracted) and the other node is less than or equal to that sequence length (minus the
   * offset), then refine the other node's type to less than the sequence length (minus the offset).
   * This is case 12.
   */
  private void refineNeqSequenceLength(
      Node lengthAccess, Node otherNode, AnnotationMirror otherNodeAnno, CFStore store) {

    // If lengthAccess is "receiver.length - c" where c is an integer constant,
    // then lengthOffset is "c".
    int lengthOffset = 0;
    if (lengthAccess instanceof NumericalSubtractionNode) {
      NumericalSubtractionNode subtraction = (NumericalSubtractionNode) lengthAccess;
      Node offsetNode = subtraction.getRightOperand();
      Long offsetValue =
          ValueCheckerUtils.getExactValue(
              offsetNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory());
      if (offsetValue != null
          && offsetValue > Integer.MIN_VALUE
          && offsetValue <= Integer.MAX_VALUE) {
        lengthOffset = offsetValue.intValue();
        lengthAccess = subtraction.getLeftOperand();
      } else {
        // Subtraction of non-constant expressions is not supported
        return;
      }
    }

    JavaExpression receiver = null;
    if (NodeUtils.isArrayLengthFieldAccess(lengthAccess)) {
      FieldAccess fa =
          (FieldAccess) JavaExpression.fromNodeFieldAccess((FieldAccessNode) lengthAccess);
      receiver = fa.getReceiver();

    } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthAccess)) {
      JavaExpression ma = JavaExpression.fromNode(lengthAccess);
      if (ma instanceof MethodCall) {
        receiver = ((MethodCall) ma).getReceiver();
      }
    }

    if (receiver != null && !receiver.containsUnknown()) {
      UBQualifier otherQualifier =
          UBQualifier.createUBQualifier(
              otherNodeAnno, (UpperBoundChecker) atypeFactory.getChecker());
      String sequence = receiver.toString();
      // Check if otherNode + c - 1 < receiver.length
      if (otherQualifier.hasSequenceWithOffset(sequence, lengthOffset - 1)) {
        // Add otherNode + c < receiver.length
        UBQualifier newQualifier =
            UBQualifier.createUBQualifier(sequence, Integer.toString(lengthOffset));
        otherQualifier = otherQualifier.glb(newQualifier);
        for (Node internal : splitAssignments(otherNode)) {
          JavaExpression leftJe = JavaExpression.fromNode(internal);
          store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(otherQualifier));
        }
      }
    }
  }

  /**
   * If some Node a is known to be less than the length of some array, x, then, the type of a + b,
   * is @LTLengthOf(value="x", offset="-b"). If b is known to be less than the length of some other
   * array, y, then the type of a + b is @LTLengthOf(value={"x", "y"}, offset={"-b", "-a"}).
   *
   * <p>Alternatively, if a is known to be less than the length of x when some offset, o, is added
   * to a (the type of a is @LTLengthOf(value="x", offset="o")), then the type of a + b
   * is @LTLengthOf(value="x",offset="o - b"). (Note, if "o - b" can be computed, then it is and the
   * result is used in the annotation.)
   *
   * <p>In addition, If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int
   * and expression j is less than or equal to the length of f1, then the type of i + j is
   * .@LTLengthOf("f2").
   *
   * <p>These three cases correspond to cases 13-15.
   */
  @Override
  public TransferResult<CFValue, CFStore> visitNumericalAddition(
      NumericalAdditionNode n, TransferInput<CFValue, CFStore> in) {
    // type of  leftNode + rightNode  is  glb(t, s) where
    // t = minusOffset(type(leftNode), rightNode) and
    // s = minusOffset(type(rightNode), leftNode)

    UBQualifier left = getUBQualifierForAddition(n.getLeftOperand(), in);
    UBQualifier t = left.minusOffset(n.getRightOperand(), atypeFactory);

    UBQualifier right = getUBQualifierForAddition(n.getRightOperand(), in);
    UBQualifier s = right.minusOffset(n.getLeftOperand(), atypeFactory);

    UBQualifier glb = t.glb(s);
    if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) {
      // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and
      // expression j is less than or equal to the length of f1, then the type of i + j is
      // @LTLengthOf("f2").
      UBQualifier r = removeSequenceLengths((LessThanLengthOf) left, (LessThanLengthOf) right);
      glb = glb.glb(r);
      UBQualifier l = removeSequenceLengths((LessThanLengthOf) right, (LessThanLengthOf) left);
      glb = glb.glb(l);
    }

    return createTransferResult(n, in, glb);
  }

  /**
   * Returns the result of adding i to j.
   *
   * <p>When expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length") int} and
   * expression j is less than or equal to the length of f1, then the type of i + j
   * is @LTLengthOf("f2").
   *
   * <p>When expression i has type {@code @LTLengthOf (value = "f2", offset = "f1.length - 1") int}
   * and expression j is less than the length of f1, then the type of i + j is @LTLengthOf("f2").
   *
   * @param i the type of the expression added to j
   * @param j the type of the expression added to i
   * @return the type of i + j
   */
  private UBQualifier removeSequenceLengths(LessThanLengthOf i, LessThanLengthOf j) {
    List<String> lessThan = new ArrayList<>();
    List<String> lessThanOrEqual = new ArrayList<>();
    for (String sequence : i.getSequences()) {
      if (i.isLessThanLengthOf(sequence)) {
        lessThan.add(sequence);
      } else if (i.hasSequenceWithOffset(sequence, -1)) {
        lessThanOrEqual.add(sequence);
      }
    }
    // Creates a qualifier that is the same a j with the array.length offsets removed. If
    // an offset doesn't have an array.length, then the offset/array pair is removed. If
    // there are no such pairs, Unknown is returned.
    UBQualifier lessThanEqQ = j.removeSequenceLengthAccess(lessThanOrEqual);
    // Creates a qualifier that is the same a j with the array.length - 1 offsets removed. If
    // an offset doesn't have an array.length, then the offset/array pair is removed. If
    // there are no such pairs, Unknown is returned.
    UBQualifier lessThanQ = j.removeSequenceLengthAccessAndNeg1(lessThan);

    return lessThanEqQ.glb(lessThanQ);
  }

  /**
   * If some Node a is known to be less than the length of some sequence x, then the type of a - b
   * is @LTLengthOf(value="x", offset="b"). If b is known to be less than the length of some other
   * sequence, this doesn't add any information about the type of a - b. But, if b is non-negative
   * or positive, then a - b should keep the types of a. This corresponds to cases 16 and 17.
   */
  @Override
  public TransferResult<CFValue, CFStore> visitNumericalSubtraction(
      NumericalSubtractionNode n, TransferInput<CFValue, CFStore> in) {
    UBQualifier left = getUBQualifier(n.getLeftOperand(), in);
    UBQualifier leftWithOffset = left.plusOffset(n.getRightOperand(), atypeFactory);
    if (atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), NonNegative.class)
        || atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), Positive.class)) {
      // If the right side of the expression is NN or POS, then all the left side's
      // annotations should be kept.
      if (left.isLessThanLengthQualifier()) {
        leftWithOffset = left.glb(leftWithOffset);
      }
    }

    // If the result of a numerical subtraction would be LTEL(b) or LTL(b), and b is HSS(a,
    // from, to), and the subtraction node itself is i - from where i is LTEL(b), then the
    // result is LTEL(a).  If i is LTL(b) instead, the result is LTL(a).

    if (leftWithOffset.isLessThanLengthQualifier()) {

      LessThanLengthOf subtractionResult = (LessThanLengthOf) leftWithOffset;

      for (String b : subtractionResult.getSequences()) {
        if (subtractionResult.hasSequenceWithOffset(b, -1)
            || subtractionResult.hasSequenceWithOffset(b, 0)) {

          TreePath currentPath = this.atypeFactory.getPath(n.getTree());
          JavaExpression je;
          try {
            je = UpperBoundVisitor.parseJavaExpressionString(b, atypeFactory, currentPath);
          } catch (NullPointerException npe) {
            // I have no idea why this seems to happen only on a few JDK classes.  It
            // appears to only happen during the preprocessing step - the NPE is thrown
            // while trying to find the enclosing class of a class tree, which is null.
            // I can't find a reproducible test case that's smaller than the size of
            // DualPivotQuicksort.  Since this refinement is optional, but useful
            // elsewhere, catching this NPE here and returning is always safe.
            return createTransferResult(n, in, leftWithOffset);
          }

          Subsequence subsequence = Subsequence.getSubsequenceFromReceiver(je, atypeFactory);

          if (subsequence != null) {
            String from = subsequence.from;
            String to = subsequence.to;
            String a = subsequence.array;

            JavaExpression leftOp = JavaExpression.fromNode(n.getLeftOperand());
            JavaExpression rightOp = JavaExpression.fromNode(n.getRightOperand());

            if (rightOp.toString().equals(from)) {
              LessThanAnnotatedTypeFactory lessThanAtypeFactory =
                  atypeFactory.getLessThanAnnotatedTypeFactory();
              AnnotationMirror lessThanType =
                  lessThanAtypeFactory
                      .getAnnotatedType(n.getLeftOperand().getTree())
                      .getPrimaryAnnotation(LessThan.class);

              if (lessThanType != null && lessThanAtypeFactory.isLessThan(lessThanType, to)) {
                UBQualifier ltlA = UBQualifier.createUBQualifier(a, "0");
                leftWithOffset = leftWithOffset.glb(ltlA);
              } else if (leftOp.toString().equals(to)
                  || (lessThanType != null
                      && lessThanAtypeFactory.isLessThanOrEqual(lessThanType, to))) {
                // It's necessary to check if leftOp == to because LessThan doesn't
                // infer that things are less than or equal to themselves.
                UBQualifier ltelA = UBQualifier.createUBQualifier(a, "-1");
                leftWithOffset = leftWithOffset.glb(ltelA);
              }
            }
          }
        }
      }
    }
    return createTransferResult(n, in, leftWithOffset);
  }

  /**
   * Computes a type of a sequence length access. This is case 18.
   *
   * @param n sequence length access node
   */
  private @Nullable TransferResult<CFValue, CFStore> visitLengthAccess(
      Node n, TransferInput<CFValue, CFStore> in, JavaExpression sequenceJe, Tree sequenceTree) {
    if (sequenceTree == null) {
      return null;
    }
    // Look up the SameLen type of the sequence.
    AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(sequenceTree);
    List<String> sameLenSequences;
    if (sameLenAnno == null) {
      sameLenSequences = Collections.singletonList(sequenceJe.toString());
    } else {
      sameLenSequences =
          AnnotationUtils.getElementValueArray(
              sameLenAnno, atypeFactory.sameLenValueElement, String.class);
      String sequenceString = sequenceJe.toString();
      if (!sameLenSequences.contains(sequenceString)) {
        sameLenSequences.add(sequenceString);
      }
    }

    List<String> offsets = Collections.nCopies(sameLenSequences.size(), "-1");

    if (CFAbstractStore.canInsertJavaExpression(sequenceJe)) {
      UBQualifier qualifier = UBQualifier.createUBQualifier(sameLenSequences, offsets);
      UBQualifier previous = getUBQualifier(n, in);
      return createTransferResult(n, in, qualifier.glb(previous));
    }

    return null;
  }

  /**
   * If n is an array length field access, then the type of a.length is the glb
   * of @LTEqLengthOf("a") and the value of a.length in the store. This is case 19.
   */
  @Override
  public TransferResult<CFValue, CFStore> visitFieldAccess(
      FieldAccessNode n, TransferInput<CFValue, CFStore> in) {
    if (NodeUtils.isArrayLengthFieldAccess(n)) {
      FieldAccess arrayLength = (FieldAccess) JavaExpression.fromNodeFieldAccess(n);
      JavaExpression arrayJe = arrayLength.getReceiver();
      Tree arrayTree = n.getReceiver().getTree();
      TransferResult<CFValue, CFStore> result = visitLengthAccess(n, in, arrayJe, arrayTree);
      if (result != null) {
        return result;
      }
    }
    return super.visitFieldAccess(n, in);
  }

  /**
   * If n is a String.length() method invocation, then the type of s.length() is the glb
   * of @LTEqLengthOf("s") and the value of s.length() in the store. This is case 20.
   */
  @Override
  public TransferResult<CFValue, CFStore> visitMethodInvocation(
      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {

    if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(n)) {
      JavaExpression stringLength = JavaExpression.fromNode(n);
      if (stringLength instanceof MethodCall) {
        JavaExpression receiverJe = ((MethodCall) stringLength).getReceiver();
        Tree receiverTree = n.getTarget().getReceiver().getTree();
        // receiverTree is null when the receiver is implicit "this".
        if (receiverTree != null) {
          TransferResult<CFValue, CFStore> result =
              visitLengthAccess(n, in, receiverJe, receiverTree);
          if (result != null) {
            return result;
          }
        }
      }
    }
    return super.visitMethodInvocation(n, in);
  }

  /**
   * Returns the UBQualifier for a node, with additional refinement useful specifically for integer
   * addition, based on the information from subcheckers of the Index Checker.
   *
   * @param n the node
   * @param in dataflow analysis transfer input
   * @return the UBQualifier for {@code node}
   */
  private UBQualifier getUBQualifierForAddition(Node n, TransferInput<CFValue, CFStore> in) {

    // The method takes the greatest lower bound of the qualifier returned by
    // getUBQualifier and a qualifier created from a SubstringIndexFor annotation, if such
    // annotation is present and the index is known to be non-negative.

    UBQualifier ubQualifier = getUBQualifier(n, in);
    Tree nodeTree = n.getTree();
    // Annotation from the Substring Index hierarchy
    AnnotatedTypeMirror substringIndexType =
        atypeFactory.getSubstringIndexAnnotatedTypeFactory().getAnnotatedType(nodeTree);
    AnnotationMirror substringIndexAnno =
        substringIndexType.getPrimaryAnnotation(SubstringIndexFor.class);
    // Annotation from the Lower bound hierarchy
    AnnotatedTypeMirror lowerBoundType =
        atypeFactory.getLowerBoundAnnotatedTypeFactory().getAnnotatedType(nodeTree);
    // If the index has an SubstringIndexFor annotation and at the same time is non-negative,
    // convert the SubstringIndexFor annotation to a upper bound qualifier.
    if (substringIndexAnno != null
        && (lowerBoundType.hasPrimaryAnnotation(NonNegative.class)
            || lowerBoundType.hasPrimaryAnnotation(Positive.class))) {
      UBQualifier substringIndexQualifier =
          UBQualifier.createUBQualifier(
              substringIndexAnno, (UpperBoundChecker) atypeFactory.getChecker());
      ubQualifier = ubQualifier.glb(substringIndexQualifier);
    }
    return ubQualifier;
  }

  /**
   * Returns the UBQualifier for node. It does this by finding a {@link CFValue} for node. First it
   * checks the store in the transfer input. If one isn't there, the analysis is checked. If the
   * UNKNOWN qualifier is returned, then the AnnotatedTypeMirror from the type factory is used.
   *
   * @param n node
   * @param in transfer input
   * @return the UBQualifier for node
   */
  private UBQualifier getUBQualifier(Node n, TransferInput<CFValue, CFStore> in) {
    QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
    JavaExpression je = JavaExpression.fromNode(n);
    CFValue value = null;
    if (CFAbstractStore.canInsertJavaExpression(je)) {
      value = in.getRegularStore().getValue(je);
    }
    if (value == null) {
      value = analysis.getValue(n);
    }
    UBQualifier qualifier = getUBQualifier(hierarchy, value);
    if (qualifier.isUnknown()) {
      // The qualifier from the store or analysis might be UNKNOWN if there was some error.
      // For example,
      //   @LTLength("a") int i = 4;  // error
      // The type of i in the store is @UpperBoundUnknown, but the type of i as computed by
      // the type factory is @LTLength("a"), so use that type.
      CFValue valueFromFactory = getValueFromFactory(n.getTree(), n);
      return getUBQualifier(hierarchy, valueFromFactory);
    }
    return qualifier;
  }

  private UBQualifier getUBQualifier(QualifierHierarchy hierarchy, CFValue value) {
    if (value == null) {
      return UpperBoundUnknownQualifier.UNKNOWN;
    }
    AnnotationMirrorSet set = value.getAnnotations();
    AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(set, atypeFactory.UNKNOWN);
    if (anno == null) {
      return UpperBoundUnknownQualifier.UNKNOWN;
    }
    return UBQualifier.createUBQualifier(anno, (UpperBoundChecker) atypeFactory.getChecker());
  }

  private TransferResult<CFValue, CFStore> createTransferResult(
      Node n, TransferInput<CFValue, CFStore> in, UBQualifier qualifier) {
    AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(qualifier);
    return createTransferResult(newAnno, n.getType(), in);
  }

  @Override
  public TransferResult<CFValue, CFStore> visitCase(
      CaseNode n, TransferInput<CFValue, CFStore> in) {
    TransferResult<CFValue, CFStore> result = super.visitCase(n, in);
    // Refines subtrahend in the switch expression
    // TODO: This cannot be done in strengthenAnnotationOfEqualTo, because that does not provide
    // transfer input.
    List<Node> caseNodes = n.getCaseOperands();
    AssignmentNode assign = n.getSwitchOperand();
    Node switchNode = assign.getExpression();
    for (Node caseNode : caseNodes) {
      refineSubtrahendWithOffset(switchNode, caseNode, false, in, result.getThenStore());
    }
    return result;
  }

  @Override
  public TransferResult<CFValue, CFStore> visitIntegerLiteral(
      IntegerLiteralNode n, TransferInput<CFValue, CFStore> pi) {
    TransferResult<CFValue, CFStore> result = super.visitIntegerLiteral(n, pi);

    int intValue = n.getValue();
    AnnotationMirror newAnno;
    switch (intValue) {
      case 0:
        newAnno = atypeFactory.ZERO;
        break;
      case -1:
        newAnno = atypeFactory.NEGATIVEONE;
        break;
      default:
        return result;
    }
    CFValue c = new CFValue(analysis, AnnotationMirrorSet.singleton(newAnno), intTM);
    return new RegularTransferResult<>(c, result.getRegularStore());
  }
}
